//@author A0097972J
package logic;

import gui.IUserInterface;
import hirondelle.date4j.DateTime;

import java.util.HashMap;
import java.util.List;

import common.AttributeType;
import common.CommandType;
import common.DateHelper;
import common.Task;
import common.TaskStatus;
import common.TaskType;
import memory.Database;

/**
 * Update Command Handler allows Task to be updated to the Database <br/>
 * Update Command Handler is used to update command as well as mark command
 * family (mark, unmark and discard) <br/>
 * 
 */

public class UpdateCommandHandler extends CommandHandler {
	private final String SYNTAX_ERROR = "Syntax error. Press F10 for help\n";
	private final String UPDATE_SUCESSFUL = "\n%1$s \n\nis updated to\n\n%2$s\n";
	private final String UPDATE_ERROR = "\n\nError occured";
	private final String PROMPT_MESSAGE = "\n\nWhich task do you want to update?\n";
	private final String ENDINGTIME_ERROR = "\n\nWarning. Please check ending time\n";
	private final String STARTINGTIME_ERROR = "\n\nWarning. Please check starting time\n";

	public UpdateCommandHandler(IUserInterface ui, String originalCommand, HashMap<AttributeType, String> attList) {
		super(ui, originalCommand, attList);
	}

	/**
	 * Needs to be explicitly called for the command to be started <br/>
	 * This method will first check for syntax before executing the command
	 */
	public void start() {
		if (isSyntaxCorrects()) {
			execute();
		} else {
			pushToHistoryAsNonExecutableCommand();
			writeToConsole(SYNTAX_ERROR);
		}
	}

	/**
	 * Check for constraint when updating Task <br/>
	 * The name must be present <br/>
	 * 
	 * @return true if the syntax is correct, otherwise false
	 */
	private boolean isSyntaxCorrects() {
		String taskName = _attrList.get(AttributeType.name);

		if (taskName != null) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Execute the command by: <br/>
	 * -Search for relevant task in Storage <br/>
	 * -Pass results to Console <br/>
	 */

	private void execute() {
		String taskName = _attrList.get(AttributeType.name);

		List<Task> relevantTasks = searchFromStorage(true, taskName);
		List<Task> discardList = getStorage().searchTask(AttributeType.status, true, TaskStatus.discard.toString());
		
		relevantTasks.removeAll(discardList);
		
		//mark command
		if(_attrList.get(AttributeType.status) == TaskStatus.done.toString()){
			List<Task> doneList = getStorage().searchTask(AttributeType.status, true, TaskStatus.done.toString());
			relevantTasks.removeAll(doneList);
		}
		//unmark command
		if(_attrList.get(AttributeType.status) == TaskStatus.undone.toString()){
			List<Task> undoneList = getStorage().searchTask(AttributeType.status, true, TaskStatus.undone.toString());
			List<Task> overdueList = getStorage().searchTask(AttributeType.status, true, TaskStatus.overdue.toString());
			relevantTasks.removeAll(undoneList);
			relevantTasks.removeAll(overdueList);
		}

		if (relevantTasks.size() > 0) {
			Database.getInstance().setPreviousSearchResult(relevantTasks);

			Task newTask = new Task(null, null, null, null, null);

			String temp = _attrList.get(AttributeType.status);
			if (temp != null) {
				newTask.setStatus(TaskStatus.valueOf(_attrList.get(AttributeType.status)));
			}

			passResultToConsole(relevantTasks, PROMPT_MESSAGE);
			pushCommandToHistory(CommandType.update, newTask);

		} else {
			writeToConsole(NO_RESULT);
			pushToHistoryAsNonExecutableCommand();
		}

	}

	/**
	 * Update Task to Storage
	 * 
	 * @param selectedTask
	 */
	protected void updateStorage(Task selectedTask) {
		int task_id = selectedTask.getTaskId();

		Task updateTask = processUpdatedTask(selectedTask);

		boolean isUpdated = getStorage().updateTask(task_id, updateTask);

		if (isUpdated) {
			pushCommandToHistory(CommandType.update, updateTask);
			String updateSucessful = String.format(UPDATE_SUCESSFUL, selectedTask.toString(true), updateTask.toString(true));
			writeToConsole(updateSucessful);
		} else {
			writeToConsole(UPDATE_ERROR);
			pushToHistoryAsNonExecutableCommand();
		}
	}

	/**
	 * Construct update Task object
	 * 
	 * @return the Task object constructed
	 */
	private Task processUpdatedTask(Task selectedTask) {
		DateTime endDate = selectedTask.getEndingTime();
		DateTime startDate = selectedTask.getStartingTime();
		String taskName = selectedTask.getName();
		String description = selectedTask.getDescription();

		// Check difference in -name attribute and check not null
		taskName = processNameDescriptionUpdated(taskName, _attrList.get(AttributeType.name));

		// Check difference in -description attribute and check not null
		description = processNameDescriptionUpdated(description, _attrList.get(AttributeType.description));

		// Check difference in -startingTime and -endingTime
		String startingTime = _attrList.get(AttributeType.startingTime);
		String endingTime = _attrList.get(AttributeType.endingTime);

		if (startingTime != null) {
			if (getDateTime(startingTime) != null) {
				startDate = getDateTime(startingTime);
			} else {
				writeToConsole(STARTINGTIME_ERROR);
			}
		} else if (endingTime != null) {
			startDate = null;
		}

		if (endingTime != null) {
			if (getDateTime(endingTime) != null) {
				endDate = getDateTime(endingTime);
			} else {
				writeToConsole(ENDINGTIME_ERROR);
			}
		}

		TaskType taskType = determineTaskType(startDate, endDate);

		Task newTask = new Task(taskName, taskType, description, startDate, endDate);
		TaskStatus newStatus = processTaskStatus(selectedTask.getStatus(), TaskStatus.valueOf(_attrList.get(AttributeType.status)), startDate, endDate);
		newTask.setStatus(newStatus);

		return newTask;
	}

	/**
	 * Process the status of the new task. is endingTime is before current date,
	 * status is overdue.
	 * 
	 * @param selectedStatus
	 * @param newStatus
	 * @param startDate
	 * @param endDate
	 * @return TaskStatus
	 */
	private TaskStatus processTaskStatus(TaskStatus selectedStatus, TaskStatus newStatus, DateTime startDate, DateTime endDate) {

		TaskStatus temp = selectedStatus;

		if (!selectedStatus.equals(newStatus)) {
			temp = newStatus;
		}

		DateHelper dh = new DateHelper();
		if (newStatus == TaskStatus.undone && dh.compareToCurrentDateTime(endDate) == -1) {
			temp = TaskStatus.overdue;
		}
		return temp;
	}

	/**
	 * Determine the task type base on starting and ending time
	 * 
	 * @param startingTime
	 * @param endingTime
	 * @return the TaskTyped guessed from the startingTime and endingTime
	 *         provided
	 */
	private TaskType determineTaskType(DateTime startingTime, DateTime endingTime) {

		if (startingTime == null && endingTime == null && _attrList.get(AttributeType.range) == null) {
			return TaskType.floating;
		} else if (startingTime == null && endingTime != null) {
			return TaskType.deadline;
		} else if (startingTime != null && (endingTime != null || _attrList.get(AttributeType.range) != null)) {
			return TaskType.timed;
		} else {
			return TaskType.invalid;
		}
	}

	/**
	 * Process updated name and description
	 * 
	 * @param selected
	 * @param update
	 * @return
	 */
	private String processNameDescriptionUpdated(String selected, String update) {
		if (!selected.equals(update) && update != null) {
			return update;
		}
		return selected;
	}

}
